import json
import logging
from pathlib import Path
from typing import Dict, Optional, Any
from urllib.parse import urlparse
from playwright.sync_api import CDPSession

from performance.runner.constants import NETWORK_MONITOR_TYPE
from performance.runner.logger import create_sample_logger, create_detail_logger
from performance.runner.network_monitor.util import first_positive
from performance.runner.network_monitor.network_request import NetworkRequest
from performance.runner.network_monitor.log_parser import NetworkLogParser


class NetworkMonitor:
    """网络监控类，用于监控和分析网络请求性能"""
    
    def __init__(self, feature_category: str, step_logger: logging.Logger, network_log_path: Path,
        enable_detail_logging: bool = False):
        self._feature_category = feature_category
        self._target_apis = []
        self._is_monitoring = False
        self.enable_detail_logging = enable_detail_logging
        self._requests: Dict[str, NetworkRequest] = {}

        # 保存步骤日志记录器
        self.network_log_path = network_log_path
        self._step_logger = step_logger
        
        # 初始化样本数据日志记录器
        self._sample_logger = create_sample_logger(network_log_path, feature_category)
        self.log_parser = NetworkLogParser(network_log_path, step_logger)

        # 详细信息日志记录器
        self._detail_logger: Optional[logging.Logger] = None

    @property
    def feature_category(self) -> str:
        """获取特性类别（只读）"""
        return self._feature_category

    @property
    def is_monitoring(self) -> bool:
        """获取监控状态（只读）"""
        return self._is_monitoring

    @property
    def step_logger(self) -> logging.Logger:
        """获取步骤日志记录器（只读）"""
        return self._step_logger

    @property
    def sample_logger(self) -> logging.Logger:
        """获取样本日志记录器（只读）"""
        return self._sample_logger

    @property
    def detail_logger(self) -> Optional[logging.Logger]:
        """获取详细信息日志记录器（只读）"""
        return self._detail_logger

    def start_monitoring(self, client: Optional[CDPSession], test_case_name: str, target_apis: list[Any]):
        """开始监控网络请求"""
        if not client:
            error = RuntimeError('Browser not started. Call startBrowser() first.')
            self._step_logger.error(str(error))
            raise error

        try:
            self._target_apis = target_apis
            self._is_monitoring = True
            client.send('Performance.enable')
            client.send('Network.enable')
            self._step_logger.info(f'Network monitoring started! Monitor test case: {test_case_name}')

            # 如果启用了详细信息日志，创建新的日志记录器
            if self.enable_detail_logging:
                self._detail_logger = create_detail_logger(self.network_log_path, self._feature_category, 
                    test_case_name)
                self._step_logger.info(f'Detail logging for test case: {test_case_name} enabled')

            # 监听网络请求事件
            client.on('Network.requestWillBeSent', self._monitor_request_will_be_sent)
            client.on('Network.requestWillBeSentExtraInfo', self._monitor_request_extra_info)
            client.on('Network.requestServedFromCache', self._monitor_request_from_cache)
            client.on('Network.resourceChangedPriority', self._monitor_priority_change)
            client.on('Network.responseReceived', self._monitor_response_received)
            client.on('Network.dataReceived', self._monitor_data_received)
            client.on('Network.loadingFinished', self._monitor_loading_finished)

        except Exception as error:
            self._step_logger.error(f'Monitoring process failed: {str(error)}')
            raise

    def stop_monitoring(self, client: Optional[CDPSession]):
        """停止网络监控"""
        if client and self._is_monitoring:
            try:
                self._target_apis = []

                client.send('Performance.disable')
                client.send('Network.disable')
                self._is_monitoring = False

                if self._detail_logger:
                    self._detail_logger = None

                self._step_logger.info('Network monitoring stopped')
            except Exception as error:
                self._step_logger.error(f'Failed to stop monitoring: {str(error)}')
                raise

    def _monitor_request_will_be_sent(self, data: Dict[str, Any]):
        """处理请求发送事件"""
        resource_type = data.get('type', 'Other')
        # 只处理 Fetch/XHR 类型的请求
        if resource_type not in ('Fetch', 'XHR'):
            return

        if self.enable_detail_logging and self._detail_logger:
            self._detail_logger.info(json.dumps({
                'event': 'Network.requestWillBeSent',
                'data': data
            }))

        # 只处理该用例的目标API
        request = data['request']
        matched_api = None
        for api in self._target_apis:
            if request['method'].lower() == api['method'] and \
                self._match_url_to_entrypoint(request['url'], api['path']):
                matched_api = api
        if not matched_api:
            return
        
        request_id = data['requestId']
        document_url = data['documentURL']
        timestamp = data['timestamp'] * 1000
        wall_time = data['wallTime']

        # 创建新的 NetworkRequest 对象
        network_request = NetworkRequest(request_id, request['url'], document_url)
        network_request.set_issue_time(timestamp, wall_time)
        network_request.resource_type = resource_type
        network_request.request_time_ranges['requestWillBeSent'] = timestamp
        
        # 更新请求信息
        self._update_network_request_with_request(network_request, request)

        # 记录请求信息
        self._requests[request_id] = network_request

    @staticmethod
    def _match_url_to_entrypoint(url: str, endpoint: str) -> bool:
        parsed_url = urlparse(url)
        url_path = parsed_url.path
        url_parts = url_path.strip('/').split('/')
        endpoint_parts = endpoint.strip('/').split('/')
        # 只比较最后的部分，便于zip遍历
        url_parts = url_parts[-len(endpoint_parts):]

        for url_part, endpoint_part in zip(url_parts, endpoint_parts):
            # 路径参数以{}包裹，可以匹配任意值
            if endpoint_part.startswith('{') and endpoint_part.endswith('}'):
                continue
            if url_part != endpoint_part:
                return False
        return True

    def _monitor_request_extra_info(self, data: Dict[str, Any]):
        """处理请求额外信息事件"""
        request_id = data['requestId']
        request = self._requests.get(request_id)
        if request:
            if self.enable_detail_logging and self._detail_logger:
                self._detail_logger.info(json.dumps({
                    'event': 'Network.requestWillBeSentExtraInfo',
                    'data': data
                }))
            extra_request_info = {
                'connectTiming': data.get('connectTiming', {})
            }
            request.add_extra_request_info(extra_request_info)

    def _monitor_request_from_cache(self, data: Dict[str, Any]):
        """处理缓存请求事件"""
        request_id = data['requestId']
        request = self._requests.get(request_id)
        if request:
            if self.enable_detail_logging and self._detail_logger:
                self._detail_logger.info(json.dumps({
                    'event': 'Network.requestServedFromCache',
                    'data': data
                }))
            request.set_from_memory_cache()

    def _monitor_priority_change(self, data: Dict[str, Any]):
        """处理优先级变化事件"""
        request_id = data['requestId']
        request = self._requests.get(request_id)
        if request:
            if self.enable_detail_logging and self._detail_logger:
                self._detail_logger.info(json.dumps({
                    'event': 'Network.resourceChangedPriority',
                    'data': data
                }))
            request.priority = data['newPriority']

    def _monitor_response_received(self, data: Dict[str, Any]):
        """处理响应接收事件"""
        request_id = data['requestId']
        request = self._requests.get(request_id)
        if request:
            if self.enable_detail_logging and self._detail_logger:
                self._detail_logger.info(json.dumps({
                    'event': 'Network.responseReceived',
                    'data': data
                }))

            timestamp = data['timestamp'] * 1000
            request.request_time_ranges['responseReceived'] = timestamp
            request.response_received_time = timestamp
            request.resource_type = data['type']
            
            response = data['response']
            # 更新响应信息
            self._update_network_with_response(request, response)

    def _monitor_data_received(self, data: Dict[str, Any]):
        """处理数据接收事件"""
        request_id = data['requestId']
        request = self._requests.get(request_id)
        if request:
            if self.enable_detail_logging and self._detail_logger:
                self._detail_logger.info(json.dumps({
                    'event': 'Network.dataReceived',
                    'data': data
                }))
            timestamp = data['timestamp'] * 1000
            data_length = data['dataLength']
            encoded_data_length = data['encodedDataLength']
            request.add_data_received(timestamp, data_length, encoded_data_length)

    def _monitor_loading_finished(self, data: Dict[str, Any]):
        """处理加载完成事件"""
        request_id = data['requestId']
        request = self._requests.get(request_id)
        if request:
            if self.enable_detail_logging and self._detail_logger:
                self._detail_logger.info(json.dumps({
                    'event': 'Network.loadingFinished',
                    'data': data
                }))

            timestamp = data['timestamp'] * 1000
            request.request_time_ranges['loadingFinished'] = timestamp
            request.end_time = timestamp
            
            # 分析请求时间信息
            self._analyze_request_timing(request_id)
            
            # 清理请求信息
            self._requests.pop(request_id, None)

    def _analyze_request_timing(self, request_id: str):
        """分析请求时间信息
        Chrome dev tool frontend source code: 
        request.timing 对应 Network.ResourceTiming, Network.responseReceived中获取
        connectTiming  对应 Network.ConnectTiming, Network.requestWillBeSentExtraInfo中获取
        request.responseReceived = max(timing.requestTime, headerReceivedTime)
        request.issueTime = requestWillBeSent.timestamp
        request.startTime = request.issueTime || request.timing.requestTime || connectTiming.requestTime
        request.endTime = loadingfinished.timestamp

        计算:
        Queuing = request.timing.requestTime - request.issueTime
        Stalled = firstPositive(timing.dnsStart, timing.connectStart, timing.sendStart)
        DNS Lookup = timing.dnsEnd - timing.dnsStart
        Initial Connection = timing.connectEnd - timing.connectStart
        SSL = timing.sslEnd - timing.sslStart
        Request Sent = timing.sendEnd - timing.sendStart
        Waiting for Server = request.responseReceived - request.timing.requestTime - 
            max(timing.sendEnd, timing.connectEnd, timing.dnsEnd, timing.proxyEnd, Stalled)
        Content Download = request.endTime - request.responseReceived
        total = firstPositive(request.endTime, request.responseReceived) - request.timing.requestTime
        
        Args:
            request_id: 请求ID
            params: 请求参数
            
        Returns:
            None
        """
        request = self._requests.get(request_id)
        if not request:
            return

        result_timing = {
            'queuing': 0.0,
            'stalled': 0.0,
            'dnsLookup': 0.0,
            'initialConnection': 0.0,
            'ssl': 0.0,
            'requestSent': 0.0,
            'waitingForServer': 0.0,
            'contentDownload': 0.0,
            'total': 0.0
        }

        timing_info = request.timing
        if not timing_info:
            start = request.issue_time if request.issue_time != -1 else (
                request.start_time if request.start_time != -1 else 0
            )
            middle = request.response_received_time if request.response_received_time != -1 else (
                request.start_time if request.start_time != -1 else float('inf')
            )
            end = float('inf') if request.end_time == -1 else request.end_time

            result_timing['total'] = end - start
            result_timing['stalled'] = middle - start
            result_timing['contentDownload'] = end - middle

            self._log_timing_info(request, result_timing)
            return

        issue_time = request.issue_time
        start_time = (timing_info.get('requestTime') or 0) * 1000
        end_time = first_positive(request.end_time, request.response_received_time)
        
        result_timing['total'] = end_time - min(issue_time, start_time)
        if issue_time < start_time:
            result_timing['queuing'] = start_time - issue_time

        response_received = request.response_received_time - start_time
        blocking_end = first_positive(
            timing_info.get('dnsStart') or 0,
            timing_info.get('connectStart') or 0,
            timing_info.get('sendStart') or 0,
            response_received
        )

        result_timing['stalled'] = blocking_end or 0.0
        result_timing['dnsLookup'] = (timing_info.get('dnsEnd') or 0) - (timing_info.get('dnsStart') or 0)
        result_timing['initialConnection'] = \
            (timing_info.get('connectEnd') or 0) - (timing_info.get('connectStart') or 0)
        result_timing['ssl'] = (timing_info.get('sslEnd') or 0) - (timing_info.get('sslStart') or 0)
        result_timing['requestSent'] = (timing_info.get('sendEnd') or 0) - (timing_info.get('sendStart') or 0)
        result_timing['waitingForServer'] = response_received - max(
            timing_info.get('sendEnd') or 0,
            timing_info.get('connectEnd') or 0,
            timing_info.get('dnsEnd') or 0,
            blocking_end
        )

        if request.end_time != -1:
            result_timing['contentDownload'] = end_time - request.response_received_time

        self._log_timing_info(request, result_timing)

    def _log_timing_info(self, request: NetworkRequest, result_timing: Dict[str, float]):
        """记录请求时间信息到日志"""
        log_entry = {
            'timestamp': request.end_time,
            'requestId': request.request_id,
            'resourceType': request.resource_type,
            'method': request.request_method,
            'url': request.url,
            'status': f"{request.status_code} {request.status_text}",
            'timing': {
                'total': f"{result_timing['total']:.3f}",
                'resourceScheduling': {
                    'queuing': f"{result_timing['queuing']:.3f}"
                },
                'connectionStart': {
                    'stalled': f"{result_timing['stalled']:.3f}",
                    'dnsLookup': f"{result_timing['dnsLookup']:.3f}",
                    'initialConnection': f"{result_timing['initialConnection']:.3f}",
                    'ssl': f"{result_timing['ssl']:.3f}"
                },
                'requestResponse': {
                    'requestSent': f"{result_timing['requestSent']:.3f}",
                    'waitingForServer': f"{result_timing['waitingForServer']:.3f}",
                    'contentDownload': f"{result_timing['contentDownload']:.3f}"
                }
            }
        }

        # 使用样本数据日志记录器记录日志
        self._sample_logger.info(json.dumps(log_entry))

    @staticmethod
    def _update_network_with_response(network_request: NetworkRequest, response: Dict[str, Any]):
        """使用响应信息更新网络请求对象"""
        if response.get('url') and response['url'] != network_request.url:
            network_request.url = response['url']
        network_request.mime_type = response.get('mimeType', '')
        network_request.charset = response.get('charset', '')
        network_request.status_code = response.get('status', 0)
        network_request.status_text = response.get('statusText', '')
        network_request.transfer_size = response.get('encodedDataLength', 0)
        network_request.connection_id = response.get('connectionId', 0)
        network_request.connection_reused = response.get('connectionReused', False)
        network_request.set_remote_address(
            response.get('remoteIPAddress', ''),
            response.get('remotePort', -1)
        )
        network_request.protocol = response.get('protocol', '')
        network_request.timing = response.get('timing', {})

    @staticmethod
    def _update_network_request_with_request(network_request: NetworkRequest, request: Dict[str, Any]):
        """使用请求信息更新网络请求对象"""
        network_request.request_method = request.get('method', '')